﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using CC.Utilities;
using CC.Yacht.Exceptions;
using CC.Yacht.Interfaces;

namespace CC.Yacht.Server
{
    public static class SessionManager
    {
        #region Constructor
        static SessionManager()
        {
            _ClearExpiredSessionsThread = new Thread(ClearExpiredSessionsProcess)
                                              {
                                                  IsBackground = true, 
                                                  Name = "CC.Yacht.Server.SessionManager.ClearExpiredSessionsThread"
                                              };

            _ClearExpiredSessionsThread.Start();
        }
        #endregion

        #region Private Constants
        private const int ClearExpiredSessionsInterval = 1 * 60 * 1000; // One minute
        private const int ExpireMinutes = 10;
        #endregion

        #region Private Fields
        private static readonly Thread _ClearExpiredSessionsThread;
        private static readonly ManualResetEvent _ExitEvent = new ManualResetEvent(false);
        private static readonly object _LockObject = new object();
        private static readonly Dictionary<Guid, Session> _Sessions = new Dictionary<Guid, Session>();
        #endregion

        #region Public Events
        public static event EventHandler<SessionAddedEventArgs> SessionAdded;
        public static event EventHandler<SessionExpiredEventArgs> SessionExpired;
        public static event EventHandler<SessionRemovedEventArgs> SessionRemoved;
        #endregion

        #region Public Properties
        public static int Count
        {
            get { return _Sessions.Count; }
        }
        #endregion

        #region Private Methods
        private static void ClearExpiredSessionsProcess()
        {
            while (!_ExitEvent.WaitOne(0))
            {
                ClearExpiredSessions();

                _ExitEvent.WaitOne(ClearExpiredSessionsInterval);
            }
        }

        private static void OnSessionAdded(SessionAddedEventArgs eventArgs)
        {
            if (SessionAdded != null)
            {
                SessionAdded(null, eventArgs);
            }
        }

        private static void OnSessionExpired(SessionExpiredEventArgs eventArgs)
        {
            if (SessionExpired != null)
            {
                SessionExpired(null, eventArgs);
            }
        }

        private static void OnSessionRemoved(SessionRemovedEventArgs eventArgs)
        {
            if (SessionRemoved != null)
            {
                SessionRemoved(null, eventArgs);
            }
        }

        private static bool RemoveSession(Guid sessionId, bool isSessionExpired)
        {
            bool returnValue = false;

            lock (_LockObject)
            {
                if (_Sessions.ContainsKey(sessionId))
                {
                    User tempUser = _Sessions[sessionId].User.ToUser();
                    _Sessions.Remove(sessionId);

                    OnSessionRemoved(new SessionRemovedEventArgs(_Sessions.Count));

                    if (isSessionExpired)
                    {
                        OnSessionExpired(new SessionExpiredEventArgs(sessionId, _Sessions.Count, tempUser));
                    }

                    returnValue = true;
                }
            }

            return returnValue;
        }
        #endregion

        #region Public Methods
        public static void Clear()
        {
            lock (_LockObject)
            {
                _Sessions.Clear();
            }
        }

        public static int ClearExpiredSessions()
        {
            int returnValue = 0;
            List<Session> tempSessions = _Sessions.Values.Where(session => session.IsExpired).ToList();

            for (int i = tempSessions.Count - 1; i >= 0; i--)
            {
                if (RemoveSession(tempSessions[i].Id, true))
                {
                    returnValue++;
                }
            }

            if (returnValue > 0)
            {
                Logging.LogMessage("Cleared " + returnValue + " expired sessions.");
            }

            return returnValue;
        }

        public static Guid GetSessionId(long userId)
        {
            Guid sessionId = Guid.Empty;

            //NOTE: Is the OrderBy direction correct?
            var tempSessions = _Sessions.Values.Where(session => session.User.UserId == userId).OrderBy(session => session.Expires);

            if (tempSessions.Count() > 0)
            {
                //TODO: Should log something if tempSessions.Count() > 1, not high priority
                sessionId = tempSessions.First().Id;
            }

            return sessionId;
        }

        public static Guid GetSessionId(string email)
        {
            Guid sessionId = Guid.Empty;

            //NOTE: Is the OrderBy direction correct?
            var tempSessions = _Sessions.Values.Where(session => session.User.Email.Equals(email, StringComparison.InvariantCultureIgnoreCase)).OrderBy(session => session.Expires);

            if (tempSessions.Count() > 0)
            {
                //TODO: Should log something if tempSessions.Count() > 1, not high priority
                sessionId = tempSessions.First().Id;
            }

            return sessionId;
        }

        public static ExtendedUser GetUser(Guid sessionId)
        {
            ExtendedUser returnValue = null;

            if (sessionId != Guid.Empty && _Sessions.ContainsKey(sessionId))
            {
                returnValue = _Sessions[sessionId].User;
            }

            return returnValue;
        }

        public static ExtendedUser GetUser(long userId)
        {
            return GetUser(GetSessionId(userId));
        }

        public static ExtendedUser GetUser(string email)
        {
            return GetUser(GetSessionId(email));
        }

        public static ExtendedUser GetUserAndKeepAlive(Guid sessionId)
        {
            ExtendedUser returnValue = null;

            if (sessionId != Guid.Empty)
            {
                KeepAlive(sessionId);

                returnValue = _Sessions[sessionId].User;
            }

            return returnValue;
        }

        public static bool IsSessionValid(Guid sessionId)
        {
            return (_Sessions.ContainsKey(sessionId) && !_Sessions[sessionId].IsExpired);
        }

        public static ExtendedUser KeepAlive(Guid sessionId)
        {
            if (!IsSessionValid(sessionId))
            {
                throw new SessionExpiredException();
            }

            _Sessions[sessionId].Expires = DateTime.Now.AddMinutes(ExpireMinutes);

            return GetUser(sessionId);
        }

        public static Guid Login(string email, string password, IYachtServiceCallback callback)
        {
            Guid newSession = Guid.Empty;
            ExtendedUser loginUser = Database.GetUser(email);

            if (loginUser != null && Encryption.Decrypt(loginUser.Password, password).Equals(password))
            {
                Guid existingSession = GetSessionId(email);
                newSession = Guid.NewGuid();

                if (existingSession != Guid.Empty)
                {
                    RemoveSession(existingSession, false);
                }

                loginUser.Callback = callback;
                
                lock (_LockObject)
                {
                    _Sessions.Add(newSession, new Session(newSession, loginUser, DateTime.Now.AddMinutes(ExpireMinutes)));
                }

                OnSessionAdded(new SessionAddedEventArgs(_Sessions.Count));
            }

            return newSession;
        }

        public static void Logout(Guid sessionId)
        {
            RemoveSession(sessionId, true);
        }

        public static void Stop()
        {
            _ExitEvent.Set();
        }
        #endregion
    }
}
